# -*- coding: utf-8 -*-

##--------------------------------------#
## Kvasir
##
## (c) 2010-2014 Cisco Systems, Inc.
##
## Exploit database controller
##
## Download from https://github.com/offensive-security/exploit-database and place in a directory
## In kvasir.yaml set the directory in exploitdb_path, e.g. exploitdb_path: "/usr/share/exploitdb"
##
## Author: Kurt Grutzmacher <grutz@jingojango.net>
##--------------------------------------#

try:
    import git
    if git.__version__ < '0.3.1':
        raise ImportError("Your version of git is %s. Upgrade to 0.3.1 or better." % git.__version__)
    have_git = True
except ImportError, e:
    have_git = False
    GIT_MISSING = 'Requires gitpython module, but not installed or incompatible version: %s' % e

from skaldship.general import exploitdb_update
import os
import mimetypes
import logging
logger = logging.getLogger("web2py.app.kvasir")
crud.settings.formstyle = formstyle_bootstrap_kvasir


@auth.requires_login()
def index():
    response.title = "%s :: The Exploit Database" % (settings.title)
    return dict()


@auth.requires_login()
def list():
    """
    List exploitdb using server-side json
    """

    if request.vars.has_key('iDisplayStart'):
        start = int(request.vars.iDisplayStart)
    else:
        start = 0
    if request.vars.has_key('iDisplayLength'):
        if request.vars.iDisplayLength == '-1':
            limit = db(db.t_vulndata).count()
        else:
            limit = start + int(request.vars.iDisplayLength)
    else:
        limit = int(auth.user.f_show_size)

    if request.vars.has_key('sSearch'):
        # sSearch global search box
        srch = request.vars.sSearch
        query = (
            #(db.t_exploitdb.id == srch) | \
            (db.t_exploitdb.f_description.contains(srch)) | \
            (db.t_exploitdb.f_author.contains(srch)) | \
            (db.t_exploitdb.f_platform.contains(srch)) | \
            (db.t_exploitdb.f_type.contains(srch))
        )
    else:
        query = (db.t_exploitdb.id > 0)

    #orderby = db.t_expoitdb.f_date
    if request.vars.iSortingCols == '1':
        # sorting by a column - this is a little trippy because tuples start at 0
        # and datatables starts at 1 so we have to subtract 1 from iSortCol_0
        cols = (
            db.t_exploitdb.f_eid,
            db.t_exploitdb.f_description,
            db.t_exploitdb.f_date,
            db.t_exploitdb.f_author,
            db.t_exploitdb.f_platform,
            db.t_exploitdb.f_type,
            db.t_exploitdb.f_port,
        )

        orderby = cols[int(request.vars.iSortCol_0)]
        if request.vars.sSortDir_0 == 'asc':
            rows=db(query).select(orderby=orderby, limitby=(start, limit), cacheable=True)
        else:
            rows=db(query).select(orderby=~orderby, limitby=(start, limit), cacheable=True)
    else:
        rows=db(query).select(orderby=~orderby, limitby=(start,limit), cacheable=True)

    aaData = []
    for r in rows:
        aaData.append( {
            '0': A(r.f_eid, _id='id', _href=URL('detail', extension='html', args=[r.f_eid]), _target="_blank").xml(),
            '1': A(r.f_description, _id='description', _href=URL('detail', extension='html', args=[r.f_eid]), _target="_blank").xml(),
            '2': r.f_date,
            '3': r.f_author,
            '4': r.f_platform,
            '5': r.f_type,
            '6': r.f_port,
        } )

    total = db(db.t_exploitdb).count()
    result = {
        'sEcho': request.vars.sEcho,
        'iTotalRecords': total,
        'iTotalDisplayRecords': total,
        'aaData': aaData,
    }

    response.title = "%s :: The Exploit Database" % (settings.title)
    return result


@auth.requires_login()
def detail():
    """
    Serve up details of a specific exploitdb item
    """
    if request.args(0) is None:
        redirect(URL('index'))

    record = db(db.t_exploitdb.f_eid == request.args(0)).select(cacheable=True).first()
    if not record:
        redirect(URL('default', 'error', vars={'msg': T('Exploit DB record not found')}))

    contents = None
    shJsFile = None
    if record.f_file:
        mtype = mimetypes.guess_type(record.f_file)
        contents = "Binary or unknown file type. Download to view."
        if not mtype[0]:
            if record.f_file.endswith(".rb"):
                mtype = ['text/ruby']
        if mtype[0] and mtype[0].startswith("text") or mtype[0] == None:
            filename = os.path.join(settings.exploitdb_path, record.f_file)
            try:
                contents = "".join(open(filename, "r").readlines())
            except IOError, e:
                contents = "Binary or unknown file type. Download to view."
        extension = record.f_file[record.f_file.rfind('.')+1:]
        shBrushes = {
          'sh':   ['shBrushBash.js', 'bash'],
          'c':    ['shBrushCpp.js', 'c'],
          'cs':   ['shBrushCSharp.js', 'csharp'],
          'css':  ['shBrushCss.js', 'css'],
          'java': ['shBrushJava.js', 'java'],
          'js':   ['shBrushJScript.js', 'javascript'],
          'pl':   ['shBrushPerl.js', 'perl'],
          'php':  ['shBrushPhp.js', 'php'],
          'py':   ['shBrushPython.js', 'python'],
          'rb':   ['shBrushRuby.js', 'ruby'],
          'sql':  ['shBrushSql.js', 'sql'],
          'vb':   ['shBrushVb.js', 'vb'],
          'xml':  ['shBrushXml.js', 'xml'],
          'html': ['shBrushXml.js', 'html'],
          'htm':  ['shBrushXml.js', 'html'],
        }
        shJsFile = shBrushes.get(extension, ['shBrushPlain.js', 'plain'])

    response.title = "%s :: ExploitDB %s :: %s" % (settings.title, str(record.f_eid), record.f_description)
    return dict(record=record, contents=XML(contents), shJsFile=shJsFile)


@auth.requires_login()
def download():
    """
    Dowload exploitdb file
    """
    if request.args(0) is None:
        redirect(URL('index'))

    record = db(db.t_exploitdb.f_eid == request.args(0)).select(cacheable=True).first()
    if not record:
        redirect(URL('default', 'error', vars={'msg': T('Exploit DB record not found')}))

    filename = os.path.basename(record.f_file)
    fullfilename = os.path.join(settings.exploitdb_path, record.f_file)
    try:
        contents = open(fullfilename, "r").readlines()
    except IOError, e:
        contents = None

    mtype = mimetypes.guess_type(fullfilename)[0]
    if not mtype:
        mtype = 'text/txt'

    response.headers['Content-Type'] = mtype
    response.headers['Content-Disposition'] = 'attachment; filename="%s"' % filename
    return contents


@auth.requires_login()
def update_db():
    """
    Update the database with latest files.csv
    """
    if settings.exploitdb_path:
        if not os.path.exists(settings.exploitdb_path):
            msg = T('Exploit DB directory in kvasir.yaml not valid')
            if request.extension in ['load', 'json']:
                return dict(status='fail', message=msg)
            redirect(URL('default', 'error', vars={'msg': msg}))

    indexfile = os.path.join(settings.exploitdb_path, 'files.csv')

    if not os.path.exists(indexfile):
        session.flash = T('Exploit DB files.csv not found')
        redirect(URL('index'))

    dialog = FORM.confirm(T('Update'),
                          {T('Cancel'): URL('index')})

    if dialog.accepted:
        msg = exploitdb_update(indexfile)
        session.flash = msg
        redirect(URL('index'))

    return dict(dialog=dialog)

@auth.requires_login()
def git_pull():
    """
    Git Pull handler
    """
    if not have_git:
        session.flash = GIT_MISSING
        redirect(URL('index'))

    if settings.exploitdb_path:
        if not os.path.exists(settings.exploitdb_path):
            msg = T('Exploit DB directory in kvasir.yaml not valid')
            redirect(URL('default', 'error', vars={'msg': msg}))

    dialog = FORM.confirm(T('Pull'),
                          {T('Cancel'): URL('index')})

    if dialog.accepted:
        try:
            repo = git.Repo(settings.exploitdb_path)
            origin = repo.remotes.origin
            origin.fetch()
            origin.pull()
            msg = T("Exploit-database updated. ")
            indexfile = os.path.join(settings.exploitdb_path, 'files.csv')
            msg += exploitdb_update(indexfile)
            session.flash = msg

        except git.CheckoutError:
            session.flash = T(
                "Pull failed, certain files could not be checked out. Check logs for details.")
        except git.UnmergedEntriesError:
            session.flash = T(
                "Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
        except git.GitCommandError:
            session.flash = T(
                "Pull failed, git exited abnormally. See logs for details.")
        except AssertionError:
            session.flash = T(
                "Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
        except Exception, e:
            session.flash = T(
                "Error: %s" % (e)
            )

        redirect(URL('index'))

    return dict(dialog=dialog)

